
/*
	Some code logic was copied from Open Market FastCGI C++ library (fcgi-2.4.0) - file "fcgios.h".
	To avoid licensing issues attached original license file - "LICENSE.TERMS".
*/


#ifndef FASTCGI_APPLICATION_MANAGER_HPP
#define FASTCGI_APPLICATION_MANAGER_HPP

#include "aconnect/config.hpp"

#include <boost/thread.hpp>

#include "aconnect/types.hpp"
#include "aconnect/error.hpp"
#include "aconnect/logger.hpp"

#include "fastcgi.hpp"


// This is the initializer for a "struct timeval" used in a select() call
// right after a new request is accept()ed to determine readablity.  Its
// a drop-dead timer.  Its only used for AF_UNIX sockets (not TCP sockets).
// Its a workaround for a kernel bug in Linux 2.0.x and SCO Unixware.
// Making this as small as possible, yet remain reliable would be best.
// 2 seconds is very conservative.  0,0 is not reliable.  The shorter the
// timeout, the faster request processing will recover.  The longer the
// timeout, the more likely this application being "busy" will cause other
// requests to abort and cause more dead sockets that need this timeout.
#if !defined(WIN32)
#	define READABLE_UNIX_FD_DROP_DEAD_TIMEVAL 2,0
#endif	

namespace fastcgi
{
	typedef void (*async_proc) (void* clientData, int len);

	namespace os
	{	
		extern const std::string BindPipePathPrefix;

		const int PipeBufferSize = 4096;
		const int MaxPathLength = 1024;

		const int DefaultSocketSelectTimeout = 60;	// sec
		const int DefaultReadWriteTimeout = 60;		// sec
		
		const int DefaultPipeWaitTimeout = 30 * 1000;	// msec
		
#ifdef WIN32
		inline aconnect::err_type getLastError () { return ::GetLastError(); }
		inline void setLastError (aconnect::err_type errCode) { ::SetLastError(errCode); }
#else // !WIN32
		inline aconnect::err_type getLastError () { return errno; }
		inline void setLastError (aconnect::err_type errCode) { errno = errCode; }
#endif /* !_WIN32 */

		const int StdinFileno = 0;
		const int StdoutFileno = 1;
		const int StderrFileno = 2;
	}

	enum ConnectionMode 
	{
		UnusedConnection = 0,
		ConnectionPipe = 1,
		ConnectionTcpSocket = 2
	};


	struct SocketDescriptor
	{
#ifdef WIN32
		union 
		{
			HANDLE fileHandle;
			SOCKET sock;
			unsigned int value;
		};	
#else
		int sock;
#endif
		ConnectionMode mode;

		SocketDescriptor() : 
#ifdef WIN32
			sock (INVALID_SOCKET),
#else
			sock (INVALID_SOCKET),
#endif
			mode (UnusedConnection){
		}
	};


	struct ApplicationConnection
	{
		SocketDescriptor sid;

		aconnect::process_id_type pid;
		aconnect::string path;
		aconnect::port_type port;
		
		inline void reset () 
		{
			sid.mode = fastcgi::UnusedConnection;
			sid.sock = INVALID_SOCKET;
			pid = 0;
			path.clear();
			port = 0;
		}
	};

	class ApplicationManager
	{
	private:
		std::vector<ApplicationConnection> _connectionsList;
		boost::mutex _registrationMutex;
		aconnect::Logger* _log;

		
	public:
		ApplicationManager(): 
			_log (0)
		{
		}

		
		//==================================================================//

		inline bool isDescriptorValid(int descriptor) 
		{ 
			return (descriptor >=0 && (size_t) descriptor < _connectionsList.size());
		};

		inline ApplicationConnection getConnection(int descriptor) throw (fastcgi_error)
		{ 
			boost::mutex::scoped_lock lock (_registrationMutex);
			assert ( isDescriptorValid(descriptor) );
			
			if ( !isDescriptorValid(descriptor) )
				throw fastcgi_error (0, "Application is not registered");

			return _connectionsList[descriptor];
		};
		
		inline void setLog (aconnect::Logger* log) 
		{
			_log = log;	
		}

		boost::mutex& registrationMutex() { return _registrationMutex; }

		//==================================================================//
		
		int registerConnection (SocketDescriptor desc, aconnect::port_type port = 0, aconnect::string_constptr pipeName = 0, int desiredIndex = -1);
		void freeConnection (int fd);

		//==================================================================//
		//	FastCGI application interaction routines						//
		//==================================================================//

		/** Performs FCGI_GET_VALUES call to FastCGI application
		*	@param  info  Reference to ApplicationInfo structure to load data (existing values won't be reset)
		*	@param  descriptor  Global Application descriptor
		*	@return void
		*/
		static void loadFcgiAppInfo (const SocketDescriptor& sd, FcgiApplicationInfo& info) throw (fastcgi_error);

		static void write (const SocketDescriptor& sd, const byte_type* data, size_t dataLength, bool flush = false) throw (fastcgi_error);
		
		static void writePadding (const SocketDescriptor& sd, int paddingLength) throw (fastcgi_error);
		
		static size_t read(const SocketDescriptor& sd, byte_type* buffer, size_t bufferSize, int sockectSelectTimeout = os::DefaultSocketSelectTimeout) throw (fastcgi_error);
		
		static bool isReadyForRead (const SocketDescriptor& sd, int sockectSelectTimeout = os::DefaultSocketSelectTimeout);
		
		static void writeBeginRequestMessage (const SocketDescriptor& sd, int requestId, bool keepConnection = true) throw (fastcgi_error);
		
		//==================================================================//
		//	OS support routines												//
		//==================================================================//

		/**	Connects to pipe/TCP socket created by FastCGI application.
		*	@return Connected socket/pipe info
		*/
		SocketDescriptor connectToFcgiApplication (int descriptor, int readWriteTimeout) throw (fastcgi_error);

		SocketDescriptor connectToFcgiApplication (const ApplicationConnection &con, int readWriteTimeout = os::DefaultReadWriteTimeout) throw (fastcgi_error);

		/**	Prepares the listener pipe/TCP socket for local process communication.
		*	@return Created descriptor (index in _connectionsList)
		*/
		int createLocalIpcFileDescriptor (ConnectionMode mode, 
			int readWriteTimeout,
			int desiredIndex = -1,
			aconnect::string_constptr path = 0,
			aconnect::port_type port = 0,
			int backLog = 5) throw (fastcgi_error);

		/** Closes the descriptor with routine appropriate for descriptor's type.
		*/
		void close (int descriptor) throw (fastcgi_error);

		/**	Closes connected descriptor.
		*	@param Socket/pipe descriptor to close
		*/
		static void closeDescriptor (SocketDescriptor sd) throw (fastcgi_error);


		/**	Connects to pipe/TCP socket created by FastCGI application.
		*	@return Created descriptor (index in _connectionsList)
		*/
		aconnect::process_id_type spawnWorkerProcess (aconnect::string_constptr execPath, int descriptor, aconnect::string_constptr commandLine = 0) throw (fastcgi_error);

		/**	Initialize global stuff
		*/
		void initFastCgiEnvironment() throw (fastcgi_error);

		/**	Terminate all started FastCGI applications
		*/
		void terminateApplications();


#ifdef WIN32
		
#endif

	};

	
} // namespace fastcgi

#endif // FASTCGI_APPLICATION_MANAGER_HPP
